Purpose

Determine ranked spearman correlation between single cell RNASeq (scRNASeq) and bulk RNASeq (bRNASeq)

Method

  1. Perform pseudo-bulk analysis from scRNASeq of population (i.e., CMP)
  2. Download bRNASeq of same population (has 2 replicates)
  3. Ranked spearman correlation between bRNASeq replicates
  4. Ranked spearman correlation between each bRNASeq replicate and pseudobulk scRNASeq sample
  5. Ranked spearman correlation between combined bRNASeq replicates and pseudobulk scRNASeq sample

Notebook setup

Creating new pipeline using seurat v4.0.2 available 2021.06.08

Load libraries required for Seuratv4

Load libraries

library(dplyr)
library(Seurat)
library(patchwork)
library(ggplot2)
library(SingleCellExperiment)
library(Matrix.utils)
library(DESeq2)
library(edgeR)
library(csaw)
library(tximport)

Set global variables

projectName <- "pseudobulk_CMP"

Store session info

sessionInfo.filename <- paste0(projectName, "_sessionInfo.txt")
sink(sessionInfo.filename)
sessionInfo()
sink()

Load local scripts

Load scRNASeq object

Dim 25

Using CMP.rds object of dim=25, describing 85% of variation

# CMP seurat object generated in "CMPSubset.Rmd"
seurat.object <- readRDS("CMP_dim25.RDS")
p <- FeaturePlot(seurat.object, features = c("Fli1", "Klf1"), blend = TRUE, pt.size = 1, combine = TRUE, keep.scale = "all")
plot(p)
png(filename = "CMP_Fli1Klf1Ratio.png", width = 2400, height = 400)
plot(p)
dev.off()
quartz_off_screen 
                3 

p <- FeaturePlot(seurat.object, features = c("Gata2", "Gata1"), blend = TRUE, pt.size = 1, combine = TRUE, keep.scale = "all")
plot(p)
png(filename = "CMP_Gata2Gata1Ratio.png", width = 2400, height = 400)
plot(p)
dev.off()
quartz_off_screen 
                3 

Calculate pseudobulk gene expression

__ Based on tutorial @ https://hbctraining.github.io/scRNA-seq/lessons/pseudobulk_DESeq2_scrnaseq.html __

# Extract raw counts and metadata to create SingleCellExperiment object

counts <- seurat.object@assays$RNA@counts 

metadata <- seurat.object@meta.data

# Set up metadata as desired for aggregation and DE analysis
metadata$cluster_id <- factor(seurat.object@active.ident)

# Create single cell experiment object
sce <- SingleCellExperiment(assays = list(counts = counts), 
                           colData = metadata)

Investigate the object

print(dim(counts(sce)))
[1] 16278 12059
print(dim(colData(sce)))
[1] 12059    12
print(head(colData(sce)))
DataFrame with 6 rows and 12 columns
                       orig.ident nCount_RNA nFeature_RNA percent.mt  clust.ID RNA_snn_res.0.5 seurat_clusters RNA_snn_res.1 RNA_snn_res.1.5 RNA_snn_res.2 RNA_snn_res.2.5
                         <factor>  <numeric>    <integer>  <numeric> <numeric>        <factor>        <factor>      <factor>        <factor>      <factor>        <factor>
CMPm2_AAACATACAAGCCT-1      CMPm2       2731         1258    2.30685        11               5              9             6               7             10              9 
CMPm2_AAACATACACCCTC-1      CMPm2      16980         3743    2.30271         0               5              25            6               19            24              25
CMPm2_AAACATACACTGTG-1      CMPm2       5920         2083    2.71959         0               3              10            11              14            18              10
CMPm2_AAACATACAGAAGT-1      CMPm2       6314         2115    2.54989         0               2              4             0               5             4               4 
CMPm2_AAACATACAGAGGC-1      CMPm2       4749         1861    2.65319        17               3              14            13              11            21              14
CMPm2_AAACATACCCAACA-1      CMPm2      13138         3549    2.55747        11               1              17            14              15            19              17
                       cluster_id
                         <factor>
CMPm2_AAACATACAAGCCT-1         9 
CMPm2_AAACATACACCCTC-1         25
CMPm2_AAACATACACTGTG-1         10
CMPm2_AAACATACAGAAGT-1         4 
CMPm2_AAACATACAGAGGC-1         14
CMPm2_AAACATACCCAACA-1         17

Note that QC was performed during Seurat analysis

sce <- calculateQCMetrics(sce)
Error: 'calculateQCMetrics' is defunct.
Use 'perCellQCMetrics' instead.
See help("Defunct")

Aggregate counts

Aggregate top-level counts

groups <- colData(sce)[, "orig.ident"]
aggr.counts <- aggregate.Matrix(t(counts(sce)), groupings = groups, fun = "sum")

dim(aggr.counts)
[1]     1 16278
sc.dds <- DESeqDataSetFromMatrix(countData = aggr.counts, design = ~orig.ident)
Error in h(simpleError(msg, call)) : 
  error in evaluating the argument 'x' in selecting a method for function 'nrow': argument "colData" is missing, with no default

Load bRNASeq data

# import rsem.genes.results files

setwd("../../bRNASeq/")
Warning: The working directory was changed to /Users/heustonef/Desktop/10XGenomicsData/bRNASeq inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
file_list <- list.files(pattern = "^CMP", recursive = TRUE, include.dirs = TRUE)
names(file_list) <- lapply(file_list, function(x) gsub('.genes.results', '', x))
file_list
                ScriptSeq/CMP1009                 ScriptSeq/CMP1010                     TruSeq/CMP443                     TruSeq/CMP448 
"ScriptSeq/CMP1009.genes.results" "ScriptSeq/CMP1010.genes.results"     "TruSeq/CMP443.genes.results"     "TruSeq/CMP448.genes.results" 
mstxi <- tximport(files = paste0("../../bRNASeq/", file_list), type = 'rsem', txIn = FALSE, txOut = FALSE, geneIdCol = 3, )
reading in files with read_tsv
1 2 3 4 
colnames(mstxi$counts) <- gsub("^.*/", "", names(file_list))
rna.counts <- merge(mstxi$counts, t(aggr.counts), by = "row.names", all = TRUE)
rna.counts[is.na(rna.counts)]<-0
rownames(rna.counts) <- rna.counts$Row.names
rna.counts <- rna.counts[, 2:ncol(rna.counts)]
head(rna.counts)
# Set reference sample =-----------------------------------------------------------
dgelist_groups <- factor(c(unlist(stringr::str_extract_all(gsub("/.*", "", names(file_list)), pattern = "[a-zA-Z]+")), "SingleCell"))

# dgelist_groups <- relevel(dgelist_groups, ref = "CMP")
levels(dgelist_groups)
[1] "ScriptSeq"  "SingleCell" "TruSeq"    
dgelist <- DGEList(counts = rna.counts, group = dgelist_groups)
dgelist
An object of class "DGEList"
$counts
              CMP1009 CMP1010 CMP443 CMP448 CMPm2
0610005C13Rik       0       0      0      0    31
0610007P14Rik     138      92    244    459     0
0610009B22Rik       0      16    118     75  5544
0610009E02Rik       0       0      0      0   144
0610009L18Rik       0       7      0     32  1106
27030 more rows ...

$samples
apply(dgelist$counts, 2, sum)
 CMP1009  CMP1010   CMP443   CMP448    CMPm2 
 1705279  5065044 31763431 28668883 92325072 
keep <- rowSums(cpm(dgelist) > 10) >=2
dgelist <- dgelist[keep,]

Calculate library size

dgelist$samples$lib.size <- colSums(dgelist$counts)
dgelist <- calcNormFactors(dgelist)

Calculate dispersion

designMat

designMat <- model.matrix(~ 0 + dgelist$samples$group)
colnames(designMat) <- levels(dgelist$samples$group)
designMat
  ScriptSeq SingleCell TruSeq
1         1          0      0
2         1          0      0
3         0          0      1
4         0          0      1
5         0          1      0
attr(,"assign")
[1] 1 1 1
attr(,"contrasts")
attr(,"contrasts")$`dgelist$samples$group`
[1] "contr.treatment"

estimate GLM dispersion and apply

dgelist <- estimateGLMCommonDisp(dgelist, designMat)
dgelist <- estimateGLMTrendedDisp(dgelist, designMat, method = 'bin.spline')
dgelist <- estimateGLMTagwiseDisp(dgelist, designMat)
fit <- glmQLFit(dgelist, designMat, robust = TRUE)
plotQLDisp(fit)

qlf <- glmQLFTest(fit, coef = 1:2)
summary(decideTests(qlf))
       SingleCell-ScriptSeq
NotSig                    1
Sig                   10024

rld on individual samples

rld <- rlog(round(dgelist$counts), blind = TRUE)
converting counts to integer mode
rld <- as.data.frame(rld)
rld.spear <- cor(rld, method = "spearman")
rld.melt <- reshape2::melt(rld)
No id variables; using all as measure variables
colnames(rld.melt) <- c("sample", "rld")
rld.spear
          CMP1009   CMP1010    CMP443    CMP448     CMPm2
CMP1009 1.0000000 0.9007321 0.7848357 0.7830653 0.6121268
CMP1010 0.9007321 1.0000000 0.8423723 0.8411575 0.6431091
CMP443  0.7848357 0.8423723 1.0000000 0.9684792 0.7156723
CMP448  0.7830653 0.8411575 0.9684792 1.0000000 0.7286673
CMPm2   0.6121268 0.6431091 0.7156723 0.7286673 1.0000000
ggplot(data = rld.melt, aes(x = sample, y = rld, fill = sample)) + geom_violin()

ggplot(data = rld, aes(x = CMPm2, y = CMP1010)) + geom_point()

rld on pooled samples

pool.counts <- dgelist$counts
pool.counts <- as.data.frame(pool.counts)
pool.counts$ScriptSeq <- (pool.counts$CMP1009 + pool.counts$CMP1010)/2
pool.counts$TruSeq <- (pool.counts$CMP443 + pool.counts$CMP448)/2
pool.counts <- subset(pool.counts, select = c(ScriptSeq, TruSeq, CMPm2))
pool.counts <- as.matrix(pool.counts)
pool.rld <- rlog(round((pool.counts)), blind = TRUE)
converting counts to integer mode
-- note: fitType='parametric', but the dispersion trend was not well captured by the
   function: y = a/x + b, and a local regression fit was automatically substituted.
   specify fitType='local' or 'mean' to avoid this message next time.
pool.rld.spear <- cor(pool.rld, method = "spearman")
pool.rld.melt <- reshape2::melt(pool.rld)
colnames(pool.rld.melt) <- c("sample", "rld")
pool.rld.spear
          ScriptSeq    TruSeq     CMPm2
ScriptSeq 1.0000000 0.7877211 0.5488382
TruSeq    0.7877211 1.0000000 0.6419253
CMPm2     0.5488382 0.6419253 1.0000000
ggplot(data = pool.rld.melt, aes(x = sample, y = rld, fill = sample)) + geom_violin()

ggplot(data = pool.rld.melt, aes(x = CMPm2, y = ScriptSeq)) + geom_point()
Error in FUN(X[[i]], ...) : object 'CMPm2' not found

Combine replicates from CPM table

rna.cpm <- as.data.frame(cpm(dgelist, log = TRUE, normalized.lib.sizes = TRUE))
rna.cpm$ScriptSeq <- (rna.cpm$CMP1009 + rna.cpm$CMP1010)/2
rna.cpm$TruSeq <- (rna.cpm$CMP443 + rna.cpm$CMP448)/2
rna.cpm <- subset(rna.cpm, select = c(ScriptSeq, TruSeq, CMPm2))

plot correlation and scatter grams

cpm.spear <- cor(rna.cpm, method = "spearman")
cpm.melt <- reshape2::melt(rld)
No id variables; using all as measure variables
colnames(cpm.melt) <- c("sample", "rld")
cpm.spear
          ScriptSeq    TruSeq     CMPm2
ScriptSeq 1.0000000 0.6132299 0.2978549
TruSeq    0.6132299 1.0000000 0.4450933
CMPm2     0.2978549 0.4450933 1.0000000

Create DESeq object from single cell data

sc.dds <- DESeqDataSetFromMatrix(countData = aggr.counts, colData = metadata, design = ~orig.ident)
LS0tCnRpdGxlOiAicHNldWRvYnVsa19DTVAiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMgUHVycG9zZQpEZXRlcm1pbmUgX3JhbmtlZCBzcGVhcm1hbiBjb3JyZWxhdGlvbl8gYmV0d2VlbiBzaW5nbGUgY2VsbCBSTkFTZXEgKHNjUk5BU2VxKSBhbmQgYnVsayBSTkFTZXEgKGJSTkFTZXEpCgojIyBNZXRob2QKMS4gUGVyZm9ybSBwc2V1ZG8tYnVsayBhbmFseXNpcyBmcm9tIHNjUk5BU2VxIG9mIHBvcHVsYXRpb24gKGkuZS4sIENNUCkKMi4gRG93bmxvYWQgYlJOQVNlcSBvZiBzYW1lIHBvcHVsYXRpb24gKGhhcyAyIHJlcGxpY2F0ZXMpCjMuIFJhbmtlZCBzcGVhcm1hbiBjb3JyZWxhdGlvbiBiZXR3ZWVuIGJSTkFTZXEgcmVwbGljYXRlcwo0LiBSYW5rZWQgc3BlYXJtYW4gY29ycmVsYXRpb24gYmV0d2VlbiBlYWNoIGJSTkFTZXEgcmVwbGljYXRlIGFuZCBwc2V1ZG9idWxrIHNjUk5BU2VxIHNhbXBsZQo1LiBSYW5rZWQgc3BlYXJtYW4gY29ycmVsYXRpb24gYmV0d2VlbiBjb21iaW5lZCBiUk5BU2VxIHJlcGxpY2F0ZXMgYW5kIHBzZXVkb2J1bGsgc2NSTkFTZXEgc2FtcGxlCgoKIyBOb3RlYm9vayBzZXR1cAoKQ3JlYXRpbmcgbmV3IHBpcGVsaW5lIHVzaW5nIHNldXJhdCB2NC4wLjIgYXZhaWxhYmxlIDIwMjEuMDYuMDgKCkxvYWQgbGlicmFyaWVzIHJlcXVpcmVkIGZvciBTZXVyYXR2NAoKIyMgTG9hZCBsaWJyYXJpZXMKYGBge3Igc2V0dXB9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQpsaWJyYXJ5KE1hdHJpeC51dGlscykKbGlicmFyeShERVNlcTIpCmxpYnJhcnkoZWRnZVIpCmxpYnJhcnkoY3NhdykKbGlicmFyeSh0eGltcG9ydCkKYGBgCgojIyBTZXQgZ2xvYmFsIHZhcmlhYmxlcwpgYGB7cn0KcHJvamVjdE5hbWUgPC0gInBzZXVkb2J1bGtfQ01QIgpgYGAKCiMjIFN0b3JlIHNlc3Npb24gaW5mbwpgYGB7cn0Kc2Vzc2lvbkluZm8uZmlsZW5hbWUgPC0gcGFzdGUwKHByb2plY3ROYW1lLCAiX3Nlc3Npb25JbmZvLnR4dCIpCnNpbmsoc2Vzc2lvbkluZm8uZmlsZW5hbWUpCnNlc3Npb25JbmZvKCkKc2luaygpCmBgYAoKIyMgTG9hZCBsb2NhbCBzY3JpcHRzCmBgYHtyfQojIHNvdXJjZSgiLi4vUkZ1bmN0aW9ucy9yZWFkXzEwWEdlbm9taWNzX2RhdGEuUiIpCiMgc291cmNlKCIuLi9SRnVuY3Rpb25zL1BlcmNlbnRWYXJpYW5jZS5SIikKc291cmNlKCIuLi9SRnVuY3Rpb25zL01vdXNlMkh1bWFuX2lkY29udmVyc2lvbi5SIikKc291cmNlICgiLi4vUkZ1bmN0aW9ucy9Db2xvclBhbGV0dGUuUiIpCmBgYAoKIyBMb2FkIHNjUk5BU2VxIG9iamVjdAoKIyMgRGltIDI1ClVzaW5nIENNUC5yZHMgb2JqZWN0IG9mIGRpbT0yNSwgZGVzY3JpYmluZyA4NSUgb2YgdmFyaWF0aW9uCmBgYHtyfQojIENNUCBzZXVyYXQgb2JqZWN0IGdlbmVyYXRlZCBpbiAiQ01QU3Vic2V0LlJtZCIKc2V1cmF0Lm9iamVjdCA8LSByZWFkUkRTKCJDTVBfZGltMjUuUkRTIikKYGBgCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTE1fQpwIDwtIEZlYXR1cmVQbG90KHNldXJhdC5vYmplY3QsIGZlYXR1cmVzID0gYygiRmxpMSIsICJLbGYxIiksIGJsZW5kID0gVFJVRSwgcHQuc2l6ZSA9IDEsIGNvbWJpbmUgPSBUUlVFLCBrZWVwLnNjYWxlID0gImFsbCIpCnBsb3QocCkKcG5nKGZpbGVuYW1lID0gIkNNUF9GbGkxS2xmMVJhdGlvLnBuZyIsIHdpZHRoID0gMjQwMCwgaGVpZ2h0ID0gNDAwKQpwbG90KHApCmRldi5vZmYoKQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTE1fQpwIDwtIEZlYXR1cmVQbG90KHNldXJhdC5vYmplY3QsIGZlYXR1cmVzID0gYygiR2F0YTIiLCAiR2F0YTEiKSwgYmxlbmQgPSBUUlVFLCBwdC5zaXplID0gMSwgY29tYmluZSA9IFRSVUUsIGtlZXAuc2NhbGUgPSAiYWxsIikKcGxvdChwKQpwbmcoZmlsZW5hbWUgPSAiQ01QX0dhdGEyR2F0YTFSYXRpby5wbmciLCB3aWR0aCA9IDI0MDAsIGhlaWdodCA9IDQwMCkKcGxvdChwKQpkZXYub2ZmKCkKYGBgCgoKIyMgQ2FsY3VsYXRlIHBzZXVkb2J1bGsgZ2VuZSBleHByZXNzaW9uCgpfXyBCYXNlZCBvbiB0dXRvcmlhbCBAIGh0dHBzOi8vaGJjdHJhaW5pbmcuZ2l0aHViLmlvL3NjUk5BLXNlcS9sZXNzb25zL3BzZXVkb2J1bGtfREVTZXEyX3Njcm5hc2VxLmh0bWwgX18KYGBge3J9CiMgRXh0cmFjdCByYXcgY291bnRzIGFuZCBtZXRhZGF0YSB0byBjcmVhdGUgU2luZ2xlQ2VsbEV4cGVyaW1lbnQgb2JqZWN0Cgpjb3VudHMgPC0gc2V1cmF0Lm9iamVjdEBhc3NheXMkUk5BQGNvdW50cyAKCm1ldGFkYXRhIDwtIHNldXJhdC5vYmplY3RAbWV0YS5kYXRhCgojIFNldCB1cCBtZXRhZGF0YSBhcyBkZXNpcmVkIGZvciBhZ2dyZWdhdGlvbiBhbmQgREUgYW5hbHlzaXMKbWV0YWRhdGEkY2x1c3Rlcl9pZCA8LSBmYWN0b3Ioc2V1cmF0Lm9iamVjdEBhY3RpdmUuaWRlbnQpCgojIENyZWF0ZSBzaW5nbGUgY2VsbCBleHBlcmltZW50IG9iamVjdApzY2UgPC0gU2luZ2xlQ2VsbEV4cGVyaW1lbnQoYXNzYXlzID0gbGlzdChjb3VudHMgPSBjb3VudHMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sRGF0YSA9IG1ldGFkYXRhKQoKYGBgCgpJbnZlc3RpZ2F0ZSB0aGUgb2JqZWN0CmBgYHtyfQpwcmludChkaW0oY291bnRzKHNjZSkpKQpwcmludChkaW0oY29sRGF0YShzY2UpKSkKcHJpbnQoaGVhZChjb2xEYXRhKHNjZSkpKQpgYGAKCk5vdGUgdGhhdCBRQyB3YXMgcGVyZm9ybWVkIGR1cmluZyBTZXVyYXQgYW5hbHlzaXMKCmBgYHtyfQojIHNjZSA8LSBwZXJDZWxsUUNNZXRyaWNzKHNjZSkKYGBgCiMjIEFnZ3JlZ2F0ZSBjb3VudHMKIyMjIEFnZ3JlZ2F0ZSB0b3AtbGV2ZWwgY291bnRzCgpgYGB7cn0KZ3JvdXBzIDwtIGNvbERhdGEoc2NlKVssICJvcmlnLmlkZW50Il0KYWdnci5jb3VudHMgPC0gYWdncmVnYXRlLk1hdHJpeCh0KGNvdW50cyhzY2UpKSwgZ3JvdXBpbmdzID0gZ3JvdXBzLCBmdW4gPSAic3VtIikKCmRpbShhZ2dyLmNvdW50cykKYGBgCgoKYGBge3J9CnNjLmRkcyA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IGFnZ3IuY291bnRzLCBjb2xEYXRhID0gbWV0YWRhdGEsIGRlc2lnbiA9IH5vcmlnLmlkZW50KQoKYGBgCgoKCgoKIyBMb2FkIGJSTkFTZXEgZGF0YQoKCmBgYHtyfQojIGltcG9ydCByc2VtLmdlbmVzLnJlc3VsdHMgZmlsZXMKCnNldHdkKCIuLi8uLi9iUk5BU2VxLyIpCmZpbGVfbGlzdCA8LSBsaXN0LmZpbGVzKHBhdHRlcm4gPSAiXkNNUCIsIHJlY3Vyc2l2ZSA9IFRSVUUsIGluY2x1ZGUuZGlycyA9IFRSVUUpCm5hbWVzKGZpbGVfbGlzdCkgPC0gbGFwcGx5KGZpbGVfbGlzdCwgZnVuY3Rpb24oeCkgZ3N1YignLmdlbmVzLnJlc3VsdHMnLCAnJywgeCkpCmZpbGVfbGlzdApgYGAKCmBgYHtyfQptc3R4aSA8LSB0eGltcG9ydChmaWxlcyA9IHBhc3RlMCgiLi4vLi4vYlJOQVNlcS8iLCBmaWxlX2xpc3QpLCB0eXBlID0gJ3JzZW0nLCB0eEluID0gRkFMU0UsIHR4T3V0ID0gRkFMU0UsIGdlbmVJZENvbCA9IDMsICkKY29sbmFtZXMobXN0eGkkY291bnRzKSA8LSBnc3ViKCJeLiovIiwgIiIsIG5hbWVzKGZpbGVfbGlzdCkpCmBgYAoKYGBge3J9CnJuYS5jb3VudHMgPC0gbWVyZ2UobXN0eGkkY291bnRzLCB0KGFnZ3IuY291bnRzKSwgYnkgPSAicm93Lm5hbWVzIiwgYWxsID0gVFJVRSkKcm5hLmNvdW50c1tpcy5uYShybmEuY291bnRzKV08LTAKcm93bmFtZXMocm5hLmNvdW50cykgPC0gcm5hLmNvdW50cyRSb3cubmFtZXMKcm5hLmNvdW50cyA8LSBybmEuY291bnRzWywgMjpuY29sKHJuYS5jb3VudHMpXQpoZWFkKHJuYS5jb3VudHMpCmBgYAoKYGBge3J9CmRnZWxpc3RfZ3JvdXBzIDwtIGZhY3RvcihjKHVubGlzdChzdHJpbmdyOjpzdHJfZXh0cmFjdF9hbGwoZ3N1YigiLy4qIiwgIiIsIG5hbWVzKGZpbGVfbGlzdCkpLCBwYXR0ZXJuID0gIlthLXpBLVpdKyIpKSwgIlNpbmdsZUNlbGwiKSkKbGV2ZWxzKGRnZWxpc3RfZ3JvdXBzKQpgYGAKCmBgYHtyfQpkZ2VsaXN0IDwtIERHRUxpc3QoY291bnRzID0gcm5hLmNvdW50cywgZ3JvdXAgPSBkZ2VsaXN0X2dyb3VwcykKZGdlbGlzdAphcHBseShkZ2VsaXN0JGNvdW50cywgMiwgc3VtKQprZWVwIDwtIHJvd1N1bXMoY3BtKGRnZWxpc3QpID4gMTApID49MgpkZ2VsaXN0IDwtIGRnZWxpc3Rba2VlcCxdCmBgYAoKCiMjIyBDYWxjdWxhdGUgbGlicmFyeSBzaXplCmBgYHtyfQpkZ2VsaXN0JHNhbXBsZXMkbGliLnNpemUgPC0gY29sU3VtcyhkZ2VsaXN0JGNvdW50cykKZGdlbGlzdCA8LSBjYWxjTm9ybUZhY3RvcnMoZGdlbGlzdCkKYGBgCgojIyMgQ2FsY3VsYXRlIGRpc3BlcnNpb24KCmRlc2lnbk1hdApgYGB7cn0KZGVzaWduTWF0IDwtIG1vZGVsLm1hdHJpeCh+IDAgKyBkZ2VsaXN0JHNhbXBsZXMkZ3JvdXApCmNvbG5hbWVzKGRlc2lnbk1hdCkgPC0gbGV2ZWxzKGRnZWxpc3Qkc2FtcGxlcyRncm91cCkKZGVzaWduTWF0CmBgYAoKZXN0aW1hdGUgR0xNIGRpc3BlcnNpb24gYW5kIGFwcGx5CmBgYHtyfQpkZ2VsaXN0IDwtIGVzdGltYXRlR0xNQ29tbW9uRGlzcChkZ2VsaXN0LCBkZXNpZ25NYXQpCmRnZWxpc3QgPC0gZXN0aW1hdGVHTE1UcmVuZGVkRGlzcChkZ2VsaXN0LCBkZXNpZ25NYXQsIG1ldGhvZCA9ICdiaW4uc3BsaW5lJykKZGdlbGlzdCA8LSBlc3RpbWF0ZUdMTVRhZ3dpc2VEaXNwKGRnZWxpc3QsIGRlc2lnbk1hdCkKYGBgCgpgYGB7cn0KZml0IDwtIGdsbVFMRml0KGRnZWxpc3QsIGRlc2lnbk1hdCwgcm9idXN0ID0gVFJVRSkKcGxvdFFMRGlzcChmaXQpCmBgYAoKYGBge3J9CnFsZiA8LSBnbG1RTEZUZXN0KGZpdCwgY29lZiA9ICkKc3VtbWFyeShkZWNpZGVUZXN0cyhxbGYpKQpgYGAKCgojIyMjIHJsZCBvbiAgaW5kaXZpZHVhbCBzYW1wbGVzCgpgYGB7cn0KcmxkIDwtIHJsb2cocm91bmQoZGdlbGlzdCRjb3VudHMpLCBibGluZCA9IFRSVUUpCnJsZCA8LSBhcy5kYXRhLmZyYW1lKHJsZCkKcmxkLnNwZWFyIDwtIGNvcihybGQsIG1ldGhvZCA9ICJzcGVhcm1hbiIpCnJsZC5tZWx0IDwtIHJlc2hhcGUyOjptZWx0KHJsZCkKY29sbmFtZXMocmxkLm1lbHQpIDwtIGMoInNhbXBsZSIsICJybGQiKQpgYGAKCmBgYHtyfQpybGQuc3BlYXIKYGBgCgoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHJsZC5tZWx0LCBhZXMoeCA9IHNhbXBsZSwgeSA9IHJsZCwgZmlsbCA9IHNhbXBsZSkpICsgZ2VvbV92aW9saW4oKQpgYGAKCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBybGQsIGFlcyh4ID0gQ01QbTIsIHkgPSBDTVAxMDEwKSkgKyBnZW9tX3BvaW50KCkKYGBgCgoKIyMjIyBybGQgb24gcG9vbGVkIHNhbXBsZXMKCmBgYHtyfQpwb29sLmNvdW50cyA8LSBkZ2VsaXN0JGNvdW50cwpwb29sLmNvdW50cyA8LSBhcy5kYXRhLmZyYW1lKHBvb2wuY291bnRzKQpwb29sLmNvdW50cyRTY3JpcHRTZXEgPC0gKHBvb2wuY291bnRzJENNUDEwMDkgKyBwb29sLmNvdW50cyRDTVAxMDEwKS8yCnBvb2wuY291bnRzJFRydVNlcSA8LSAocG9vbC5jb3VudHMkQ01QNDQzICsgcG9vbC5jb3VudHMkQ01QNDQ4KS8yCnBvb2wuY291bnRzIDwtIHN1YnNldChwb29sLmNvdW50cywgc2VsZWN0ID0gYyhTY3JpcHRTZXEsIFRydVNlcSwgQ01QbTIpKQpwb29sLmNvdW50cyA8LSBhcy5tYXRyaXgocG9vbC5jb3VudHMpCmBgYAoKCmBgYHtyfQpwb29sLnJsZCA8LSBybG9nKHJvdW5kKChwb29sLmNvdW50cykpLCBibGluZCA9IFRSVUUpCnBvb2wucmxkLnNwZWFyIDwtIGNvcihwb29sLnJsZCwgbWV0aG9kID0gInNwZWFybWFuIikKcG9vbC5ybGQubWVsdCA8LSByZXNoYXBlMjo6bWVsdChwb29sLnJsZCkKY29sbmFtZXMocG9vbC5ybGQubWVsdCkgPC0gYygic2FtcGxlIiwgInJsZCIpCmBgYAoKYGBge3J9CnBvb2wucmxkLnNwZWFyCmBgYAoKCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBwb29sLnJsZC5tZWx0LCBhZXMoeCA9IHNhbXBsZSwgeSA9IHJsZCwgZmlsbCA9IHNhbXBsZSkpICsgZ2VvbV92aW9saW4oKQpgYGAKCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBwb29sLnJsZC5tZWx0LCBhZXMoeCA9IENNUG0yLCB5ID0gU2NyaXB0U2VxKSkgKyBnZW9tX3BvaW50KCkKYGBgCgoKCkNvbWJpbmUgcmVwbGljYXRlcyBmcm9tIENQTSB0YWJsZQkKCmBgYHtyfQpybmEuY3BtIDwtIGFzLmRhdGEuZnJhbWUoY3BtKGRnZWxpc3QsIGxvZyA9IFRSVUUsIG5vcm1hbGl6ZWQubGliLnNpemVzID0gVFJVRSkpCnJuYS5jcG0kU2NyaXB0U2VxIDwtIChybmEuY3BtJENNUDEwMDkgKyBybmEuY3BtJENNUDEwMTApLzIKcm5hLmNwbSRUcnVTZXEgPC0gKHJuYS5jcG0kQ01QNDQzICsgcm5hLmNwbSRDTVA0NDgpLzIKcm5hLmNwbSA8LSBzdWJzZXQocm5hLmNwbSwgc2VsZWN0ID0gYyhTY3JpcHRTZXEsIFRydVNlcSwgQ01QbTIpKQpgYGAKCnBsb3QgY29ycmVsYXRpb24gYW5kIHNjYXR0ZXIgZ3JhbXMKYGBge3J9CmNwbS5zcGVhciA8LSBjb3Iocm5hLmNwbSwgbWV0aG9kID0gInNwZWFybWFuIikKY3BtLm1lbHQgPC0gcmVzaGFwZTI6Om1lbHQocmxkKQpjb2xuYW1lcyhjcG0ubWVsdCkgPC0gYygic2FtcGxlIiwgInJsZCIpCmBgYAoKYGBge3J9CmNwbS5zcGVhcgpgYGAKCgoKCgoKCgojIyBDcmVhdGUgREVTZXEgb2JqZWN0IGZyb20gc2luZ2xlIGNlbGwgZGF0YQpgYGB7cn0Kc2MuZGRzIDwtIERFU2VxRGF0YVNldEZyb21NYXRyaXgoY291bnREYXRhID0gYWdnci5jb3VudHMsIGNvbERhdGEgPSBtZXRhZGF0YSwgZGVzaWduID0gfm9yaWcuaWRlbnQpCmBgYAoKCgoK